<!-- Licensed under a BSD license. See license.html for license -->
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
  <title>WebGL - Skinning</title>
  <link type="text/css" href="../../resources/webgl-tutorials.css" rel="stylesheet" />
</head>

<body>
  <div id="info">
    <a href="https://webglfundamentals.org/webgl/lessons/zh_cn/webgl-skinning.html" target="_blank">WebGL -
      蒙皮</a>
  </div>
  <canvas id="canvas"></canvas>
</body>
<!-- vertex shader -->
<script id="vs" type="notjs">
attribute vec4 a_position;
attribute vec4 a_weight;
attribute vec4 a_boneNdx;

uniform mat4 projection;
uniform mat4 view;
uniform sampler2D boneMatrixTexture;
uniform float numBones;

// these offsets assume the texture is 4 pixels across
#define ROW0_U ((0.5 + 0.0) / 4.)
#define ROW1_U ((0.5 + 1.0) / 4.)
#define ROW2_U ((0.5 + 2.0) / 4.)
#define ROW3_U ((0.5 + 3.0) / 4.)

mat4 getBoneMatrix(float boneNdx) {
  float v = (boneNdx + 0.5) / numBones;
  return mat4(
    texture2D(boneMatrixTexture, vec2(ROW0_U, v)),
    texture2D(boneMatrixTexture, vec2(ROW1_U, v)),
    texture2D(boneMatrixTexture, vec2(ROW2_U, v)),
    texture2D(boneMatrixTexture, vec2(ROW3_U, v)));
}

void main() {

  gl_Position = projection * view *
                (getBoneMatrix(a_boneNdx[0]) * a_position * a_weight[0] +
                 getBoneMatrix(a_boneNdx[1]) * a_position * a_weight[1] +
                 getBoneMatrix(a_boneNdx[2]) * a_position * a_weight[2] +
                 getBoneMatrix(a_boneNdx[3]) * a_position * a_weight[3]);

}
</script>
<script id="fs" type="notjs">
precision mediump float;
uniform vec4 color;
void main () {
  gl_FragColor = color;
}
</script>
<script id="vs2" type="notjs">
attribute vec4 a_position;

uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;

void main() {
  gl_Position = projection * view * model * a_position;
}
</script>
<!--
for most samples webgl-utils only provides shader compiling/linking and
canvas resizing because why clutter the examples with code that's the same in every sample.
See http://webglfundamentals.org/webgl/lessons/webgl-boilerplate.html
and http://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html
for webgl-utils, m3, m4, and webgl-lessons-ui.
-->
<script src="../../resources/webgl-utils.js"></script>
<script src="../../resources/webgl-lessons-ui.js"></script>
<script src="../../resources/m4.js"></script>
<script>
  "use strict";

  function main() {
    // Get A WebGL context
    /** @type {HTMLCanvasElement} */
    var canvas = document.getElementById("canvas");
    var gl = canvas.getContext("webgl");
    if (!gl) {
      return;
    }
    var ext = gl.getExtension('OES_texture_float');
    if (!ext) {
      return;  // the extension doesn't exist on this device
    }

    // compiles and links the shaders, looks up attribute and uniform locations
    var programInfo = webglUtils.createProgramInfo(gl, ["vs", "fs"]);

    var arrays = {
      position: {
        numComponents: 2,
        data: [
          0, 1,  // 0
          0, -1,  // 1
          2, 1,  // 2
          2, -1,  // 3
          4, 1,  // 4
          4, -1,  // 5
          6, 1,  // 6
          6, -1,  // 7
          8, 1,  // 8
          8, -1,  // 9
        ],
      },
      boneNdx: {
        numComponents: 4,
        data: [
          0, 0, 0, 0,  // 0
          0, 0, 0, 0,  // 1
          0, 1, 0, 0,  // 2
          0, 1, 0, 0,  // 3
          1, 0, 0, 0,  // 4
          1, 0, 0, 0,  // 5
          1, 2, 0, 0,  // 6
          1, 2, 0, 0,  // 7
          2, 0, 0, 0,  // 8
          2, 0, 0, 0,  // 9
        ],
      },
      weight: {
        numComponents: 4,
        data: [
          1, 0, 0, 0,  // 0
          1, 0, 0, 0,  // 1
          .5, .5, 0, 0,  // 2
          .5, .5, 0, 0,  // 3
          1, 0, 0, 0,  // 4
          1, 0, 0, 0,  // 5
          .5, .5, 0, 0,  // 6
          .5, .5, 0, 0,  // 7
          1, 0, 0, 0,  // 8
          1, 0, 0, 0,  // 9
        ],
      },

      indices: {
        numComponents: 2,
        data: [
          0, 1,
          0, 2,
          1, 3,
          2, 3, //
          2, 4,
          3, 5,
          4, 5,
          4, 6,
          5, 7, //
          6, 7,
          6, 8,
          7, 9,
          8, 9,
        ],
      },
    };
    // calls gl.createBuffer, gl.bindBuffer, gl.bufferData
    var bufferInfo = webglUtils.createBufferInfoFromArrays(gl, arrays);

    // 4 matrices, one for each bone
    var numBones = 4;
    var boneArray = new Float32Array(numBones * 16);

    // prepare the texture for bone matrices
    var boneMatrixTexture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, boneMatrixTexture);
    // since we want to use the texture for pure data we turn
    // off filtering
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    // also turn off wrapping since the texture might not be a power of 2
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

    var uniforms = {
      projection: m4.orthographic(-20, 20, -10, 10, -1, 1),
      view: m4.translation(-6, 0, 0),
      boneMatrixTexture,
      numBones,
      color: [1, 0, 0, 1],
    };

    // make views for each bone. This lets all the bones
    // exist in 1 array for uploading but as separate
    // arrays for using with the math functions
    var boneMatrices = [];     // the texture data as matrices
    var bones = [];            // the value before multiplying by inverse bind matrix
    var bindPose = [];         // the bind matrix
    for (var i = 0; i < numBones; ++i) {
      boneMatrices.push(new Float32Array(boneArray.buffer, i * 4 * 16, 16));
      bindPose.push(m4.identity());  // just allocate storage
      bones.push(m4.identity());     // just allocate storage
    }

    // rotate each bone by a and simulate a hierarchy
    function computeBoneMatrices(bones, angle) {
      var m = m4.identity();
      m4.zRotate(m, angle, bones[0]);
      m4.translate(bones[0], 4, 0, 0, m);
      m4.zRotate(m, angle, bones[1]);
      m4.translate(bones[1], 4, 0, 0, m);
      m4.zRotate(m, angle, bones[2]);
      // bones[3] is not used
    }

    // compute the initial positions of each matrix
    computeBoneMatrices(bindPose, 0);

    // compute their inverses
    var bindPoseInv = bindPose.map(function (m) {
      return m4.inverse(m);
    });

    function render(time) {
      webglUtils.resizeCanvasToDisplaySize(gl.canvas);
      gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
      const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
      m4.orthographic(-aspect * 10, aspect * 10, -10, 10, -1, 1, uniforms.projection);

      var t = time * 0.001;
      var angle = Math.sin(t) * 0.8;
      computeBoneMatrices(bones, angle);

      // multiply each by its bindPoseInverse
      bones.forEach(function (bone, ndx) {
        m4.multiply(bone, bindPoseInv[ndx], boneMatrices[ndx]);
      });

      gl.useProgram(programInfo.program);
      // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
      webglUtils.setBuffersAndAttributes(gl, programInfo, bufferInfo);

      // update the texture with the current matrices
      gl.bindTexture(gl.TEXTURE_2D, boneMatrixTexture);
      gl.texImage2D(
        gl.TEXTURE_2D,
        0,         // level
        gl.RGBA,   // internal format
        4,         // width 4 pixels, each pixel has RGBA so 4 pixels is 16 values
        numBones,  // one row per bone
        0,         // border
        gl.RGBA,   // format
        gl.FLOAT,  // type
        boneArray);

      // calls gl.uniformXXX, gl.activeTexture, gl.bindTexture
      webglUtils.setUniforms(programInfo, uniforms);

      // calls gl.drawArrays or gl.drawIndices
      webglUtils.drawBufferInfo(gl, bufferInfo, gl.LINES);

      drawAxis(uniforms.projection, uniforms.view, bones);

      requestAnimationFrame(render);
    }
    requestAnimationFrame(render);


    // --- ignore below this line - it's not relevant to the exmample and it's kind of a bad example ---

    var axisProgramInfo;
    var axisBufferInfo;
    function drawAxis(projection, view, bones) {
      if (!axisProgramInfo) {
        axisProgramInfo = webglUtils.createProgramInfo(gl, ['vs2', 'fs']);
        axisBufferInfo = webglUtils.createBufferInfoFromArrays(gl, {
          position: {
            numComponents: 2,
            data: [
              0, 0,
              1, 0,
            ],
          },
        });
      }

      var uniforms = {
        projection: projection,
        view: view,
      };

      gl.useProgram(axisProgramInfo.program);
      webglUtils.setBuffersAndAttributes(gl, axisProgramInfo, axisBufferInfo);

      for (var i = 0; i < 3; ++i) {
        drawLine(bones[i], 0, [0, 1, 0, 1]);
        drawLine(bones[i], Math.PI * 0.5, [0, 0, 1, 1]);
      }

      function drawLine(mat, angle, color) {
        uniforms.model = m4.zRotate(mat, angle);
        uniforms.color = color;
        webglUtils.setUniforms(axisProgramInfo, uniforms);
        webglUtils.drawBufferInfo(gl, axisBufferInfo, gl.LINES);
      }
    }
  }

  main();
</script>

</html>